package ru.housewife.utils;

import ru.housewife.cache.CacheManager;
import ru.housewife.form.*;
import ru.housewife.model.UsersTable;
import ru.housewife.reports.CookingDishRecipeReportObject;
import ru.housewife.reports.IngredientReportObject;
import ru.housewife.reports.WareReportObject;

import java.util.*;

/**
 * TODO: add description
 *
 * @author atroshanin
 */
public class ReportUtils {


    public static List<UsersTableForm> getUserTablesForReport(UserForm user, Date reportDateFrom, Date reportDateTo) {

        List<UsersTableForm> result = new ArrayList<UsersTableForm>(user.getUsersTables().size());
        for (UsersTableForm usersTable : user.getUsersTables()) {
            if (checkUserTableForReport(reportDateFrom, reportDateTo, usersTable)) {
                result.add(usersTable);
            }
        }

        return result;
    }

    public static boolean checkUserTablesForReport(Date reportDateFrom, Date reportDateTo, List<UsersTableForm> usersTables) {
        for (UsersTableForm usersTable : usersTables) {
            if (!checkUserTableForReport(reportDateFrom, reportDateTo, usersTable)) {
                return false;
            }
        }
        return true;
    }

    private static boolean checkUserTableForReport(Date reportDateFrom, Date reportDateTo, UsersTableForm usersTable) {
        return (reportDateFrom.getTime() >= usersTable.getFrom().getTime() && reportDateFrom.getTime() <= usersTable.getTo().getTime()) ||
               (reportDateTo.getTime() >= usersTable.getFrom().getTime() && reportDateTo.getTime() <= usersTable.getTo().getTime()) ||
               (usersTable.getFrom().getTime() >= reportDateFrom.getTime() && usersTable.getFrom().getTime() <= reportDateTo.getTime());
    }

    public static List<DayMenuForm> getDaysFromUserTableForReport(UsersTableForm usersTable, Date reportDateFrom, Date reportDateTo) {

        if (reportDateFrom.getTime() > reportDateTo.getTime()) {
            throw new RuntimeException("time from could not be after time to...");
        }

        if (usersTable.getFrom().getTime() > reportDateFrom.getTime()) {
            reportDateFrom = usersTable.getFrom();
        }
        if (usersTable.getTo().getTime() < reportDateTo.getTime()) {
            reportDateTo = usersTable.getTo();
        }
        List<DayMenuForm> result = new ArrayList<DayMenuForm>();
        Calendar c1 = Calendar.getInstance();
        c1.setTime(reportDateFrom);
        DayMenuForm[] dayMenusByDay = usersTable.getDayMenusByDay();
        if (dayMenusByDay.length != usersTable.getType().getPeriodLength() && usersTable.getType().getPeriodLength() != 0) {
            throw new RuntimeException("BAD user table. It has type " + usersTable.getType() + " BUT does not have " +
                    usersTable.getType().getPeriodLength() + " days, instead it has " + dayMenusByDay.length + "days");
        }
        switch (usersTable.getType()) {
            case MONTH_31: {
                while (c1.getTimeInMillis() <= reportDateTo.getTime()) {
                    int day = c1.get(Calendar.DAY_OF_MONTH) - 1;
                    DayMenuForm dayMenu = dayMenusByDay[day];
                    if (dayMenu != null) {
                        result.add(dayMenu);
                    }
                    c1.add(Calendar.DATE, 1);
                }
            } break;
            case WEEK_7: {
                while (c1.getTimeInMillis() <= reportDateTo.getTime()) {
                    int day = c1.get(Calendar.DAY_OF_WEEK) - 2;
                    day = (day < 0) ? 6 : day;
                    DayMenuForm dayMenu = dayMenusByDay[day];
                    if (dayMenu != null) {
                        result.add(dayMenu);
                    }
                    c1.add(Calendar.DATE, 1);
                }
            } break;
            case N_DAYS_PERIOD: {

                int days = (int) ((c1.getTimeInMillis() - usersTable.getFrom().getTime()) / (24 * 3600 * 1000));
                int curDay = days % dayMenusByDay.length;
                while (c1.getTimeInMillis() <= reportDateTo.getTime()) {
                    DayMenuForm dayMenu = dayMenusByDay[curDay];
                    if (dayMenu != null) {
                        result.add(dayMenu);
                    }
                    c1.add(Calendar.DATE, 1);
                    curDay = (curDay < dayMenusByDay.length - 1) ? curDay + 1 : 0;
                }

            } break;
        }

        return result;
    }

    public static List<IngredientReportObject> generateReport(List<DayMenuForm> dayMenus, UserForm user) {

        Map<Long, CookingDishRecipeReportObject> recipeROs = new LinkedHashMap<Long, CookingDishRecipeReportObject>();
        for (DayMenuForm dayMenu : dayMenus) {
            for (EatingMenuForm eatingMenu : dayMenu.getEatingMenus()) {
                for (EatingMenuDishRecipeForm emdr : eatingMenu.getEatingMenuRecipes()) {
                    CookingDishRecipeForm recipe = emdr.getRecipe();
                    CookingDishRecipeReportObject recipeRO = recipeROs.get(recipe.getId());
                    if (recipeRO == null) {
                        recipeRO = new CookingDishRecipeReportObject(recipe);
                        recipeROs.put(recipe.getId(), recipeRO);
                    }
                    recipeRO.addCount(emdr.getCount());
                }
            }
        }

        Map<Long, IngredientReportObject> ingredientROs = new HashMap<Long, IngredientReportObject>();
        Map<Long, WareFormView> waresViewsToFillPrices = new HashMap<Long, WareFormView>();
        for (CookingDishRecipeReportObject recipeRO : recipeROs.values()) {
            CookingDishRecipeForm recipe = recipeRO.getRecipe();
            for (CookingDishComponentForm component : recipe.getComponents()) {
                IngredientForm ingredient = component.getIngredient();
                IngredientReportObject ingredientRO = ingredientROs.get(ingredient.getId());
                if (ingredientRO == null) {
                    ingredientRO = new IngredientReportObject(ingredient);
                    ingredientROs.put(ingredient.getId(), ingredientRO);
                }

                PreferableWareForComponentForm prefWareForComponent = user.getPreferableWareForComponent(component.getId());
                WareForm ware;
                if (prefWareForComponent != null) {
                    ware = prefWareForComponent.getWare();
                } else {
                    ware = component.getPreferableWare();
                }

                WareReportObject wareRO = ingredientRO.getWareRO(ware.getId());
                if (wareRO == null) {
                    wareRO = new WareReportObject(new WareFormView(ware));
                    ingredientRO.addWareReportObject(wareRO);
                    UserForm.UsersWare usersWare = user.getUsersWare(ware.getId());
                    if (usersWare != null) {
                        wareRO.getWareView().setRegionPrice(usersWare.getWareRegionPrice());
                        wareRO.getWareView().setUserPrice(usersWare.getWareUserPrice());
                    } else {
                        // TODO: ALARM Warning - this never might occur. BUt handle this anyway
                        waresViewsToFillPrices.put(ware.getId(), wareRO.getWareView());
                    }
                }

                wareRO.addCount(component.getCount());
            }
        }
        if (waresViewsToFillPrices.size() > 0) {
            // TODO: log ALARM here
            CacheManager.getInstance().fillWaresViewsWithRegionPrices(user.getRegion(), waresViewsToFillPrices);
        }
        List<IngredientReportObject> result = new ArrayList<IngredientReportObject>(ingredientROs.size());
        for (IngredientReportObject ingredientRO : ingredientROs.values()) {
            ingredientRO.sortWares();
            result.add(ingredientRO);
        }
        Collections.sort(result);
        return result;
    }

}
